/* 
 * File:   BoundingVolume.cpp
 * Author: brady
 * 
 * Created on March 4, 2011, 3:40 PM
 */



#include <vector>

#include "BoundingVolumeNode.h"
#include "LambertianShader.h"


BoundingVolumeNode::BoundingVolumeNode() {
    tval = 0;
}

BoundingVolumeNode::BoundingVolumeNode(vector<Shape*>& nodes, int sortBy)
{
    tval = 0.0;
    this->leftChild = NULL;
    this->rightChild = NULL;
    this->sortBy = sortBy;
    if(nodes.size() == 1)
    {
        this->leftChild = nodes[0];
    }
    else
    {
        {
            //this is scoped so sc is deleted after nodes is sorted
            if(nodes.size() == 0)
            {
                cout << "SORTING EMPTY LIST\n";
            }
            ShapeComparitor sc = ShapeComparitor(sortBy);
            sort(nodes.begin(), nodes.end(), sc);
        }
            vector<Shape*> leftNodes;
            vector<Shape*> rightNodes;
            int pivotIndex = ((nodes.size() - 1) / 2);
            for (int i = 0; i < nodes.size(); i++) {
                if (i <= pivotIndex) {
                    leftNodes.push_back(nodes[i]);
                } else {
                    rightNodes.push_back(nodes[i]);
                }
            }
            leftChild = new BoundingVolumeNode(leftNodes, (sortBy+1) % 3);
            rightChild = new BoundingVolumeNode(rightNodes, (sortBy+1) % 3);
    }
        boundingBox = new BoundingBox(*leftChild->GetBoundingBox());
        if(rightChild != NULL)
        {
            boundingBox->Absorb(rightChild->GetBoundingBox());
        }
        type = "BVHNode";
}

BoundingVolumeNode::~BoundingVolumeNode() {
}
/**
 * Expands the volume to encompass the entirety of the child node being added,
 * and pushes a pointer to the child onto the childNodes vector.
 * @param child child BoundingVolumeNode to be added.
 */

bool BoundingVolumeNode::TestIntersect(Ray &ray, double tmin, double tmax, HitStruct& hitStruct /* = 0 */)
{
    bool hit;
    BoundingBox* BB = this->GetBoundingBox();
    Vector3D Orig = ray.GetOrigin();
    Vector3D Dir = ray.GetDirection();
    double origX, origY, origZ, dirX, dirY, dirZ;
    double TMin, TMax, newTMin, newTMax;
    dirX = Dir[0];
    dirY = Dir[1];
    dirZ = Dir[2];
    origX = Orig[0];
    origY = Orig[1];
    origZ = Orig[2];

    if(dirX > 0.0)
    {
        TMin = (BB->GetXMin() - origX)/dirX;
        TMax = (BB->GetXMax() - origX)/dirX;
    }
    else if (dirX < 0.0)
    {
        TMin = (BB->GetXMax() - origX) / dirX;
        TMax = (BB->GetXMin() - origX) /dirX;
    }
    else
    {
        TMin = DBL_MIN;
        TMax = DBL_MAX;
    }
    if(dirY > 0.0)
    {
        newTMin = (BB->GetYMin() - origY)/dirY;
        if(newTMin > TMin) TMin = newTMin;
        newTMax = (BB->GetYMax() - origY)/dirY;
        if(newTMax < TMax) TMax = newTMax;
    }
    else if (dirY < 0.0)
    {
        newTMin = (BB->GetYMax() - origY) / dirY;
        if(newTMin > TMin) TMin = newTMin;
        newTMax = (BB->GetYMin() - origY) / dirY;
        if(newTMax < TMax) TMax = newTMax;
    }
    else
    {
        newTMin = DBL_MIN;
        newTMax = DBL_MAX;
    }

   if(dirZ > 0.0)
    {
        newTMin = (BB->GetZMin() - origZ)/dirZ;
        if(newTMin > TMin) TMin = newTMin;
        newTMax = (BB->GetZMax() - origZ)/dirZ;
        if(newTMax < TMax) TMax = newTMax;
    }
    else if (dirZ < 0.0)
    {
        newTMin = (BB->GetZMax() - origZ) / dirZ;
        if(newTMin > TMin) TMin = newTMin;
        newTMax = (BB->GetZMin() - origZ) / dirZ;
        if(newTMax < TMax) TMax = newTMax;
    }
    else
    {
        newTMin = DBL_MIN;
        newTMax = DBL_MAX;
    }

    hit = (TMin < TMax);
    //we only allow hits that enter the box between our tmin and tmax values
    
    if(hit)
    {
        HitStruct leftHS;
        HitStruct rightHS;
        bool rightFound = false;
        bool leftFound = false;
        leftFound = leftChild->TestIntersect(ray, tmin, tmax, hitStruct);
        if(leftFound){
            leftHS = hitStruct;
        }
        if(rightChild != NULL)
        {
            rightFound = rightChild->TestIntersect(ray, tmin, tmax, hitStruct);
        }
        if(rightFound)
            {
            rightHS = hitStruct;
                if(leftHS.tVal < rightHS.tVal)
                {
                    //set hitStruct back to the left values
                    hitStruct = leftHS;
                }
                else
                {
                    //set them to right values
                    hitStruct = rightHS;
                }
            }
        else
        {
            hitStruct = leftHS;
        }
        return (rightFound || leftFound);
    }
    else //we did not hit the box
    {
        return false;
    }
}

//inline
//const Vector3D BoundingVolumeNode::SurfaceNormal(Ray &ray)
//{
//    return ray.GetOrigin() * -1.0;
//}


void BoundingVolumeNode::SetShader(Shader* shader){}

inline
Shader* BoundingVolumeNode::GetShader(){return shader;}

inline
double BoundingVolumeNode::GetSortValue(int sortBy)
{
    if(sortBy == X_VAL){
        return (GetBoundingBox()->GetXMax() + GetBoundingBox()->GetXMin())/2.0;
    }
    if(sortBy == Y_VAL){
        return (GetBoundingBox()->GetYMax() + GetBoundingBox()->GetYMin())/2.0;
    }
    if(sortBy == Z_VAL){
        return (GetBoundingBox()->GetZMax() + GetBoundingBox()->GetZMin())/2.0;
    }
}
